/*
 * daTool - Extracts the DAT files for Mithis' Nexus: The Jupiter Incident
 * Copyright (C) 2004 Stephen Dawkins
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */

#include <stdio.h>
#include <windows.h>
#include <stdlib.h>
#include <fcntl.h>

//nexus xor values
#define KEY_START 0x2a;

unsigned char key = KEY_START;
unsigned char keyStep = 0x55;
unsigned long files = 0;
unsigned long tocSize = 0;

struct tEntry
{
	int type;
	char *filename;
	char *fullFilename;
	unsigned long offset;
	unsigned long length;
	unsigned long count;
	unsigned char encrypted;
	struct tEntry *children;
	struct tEntry *next;
};

struct tEntry root;

void resetFile(HANDLE hFile)
{
	key = KEY_START;
	SetFilePointer(hFile, 0, 0, FILE_BEGIN);
}

void seekFile(HANDLE hFile, unsigned long offset)
{
	int i;

	unsigned long currentPos = SetFilePointer(hFile, 0, 0, FILE_CURRENT);

	if (currentPos != offset)
	{
		key = KEY_START;
		for (i = 0; i < offset; i++)
			key += keyStep;
		SetFilePointer(hFile, offset, 0, FILE_BEGIN);
	}
}

unsigned char *ReadData(HANDLE hFile, unsigned long length, int encrypted)
{
	unsigned char *buffer = (unsigned char *) malloc(length);
	unsigned long lread = 0;

	BOOL bResult = ReadFile(hFile, buffer, length, &lread, NULL);
	if (bResult && lread != 0 && length == lread)
	{
		int i;
		if (encrypted)
			for (i = 0; i < length; i++, key += keyStep)
				buffer[i] = buffer[i] ^ key;
		else //make sure the key is updated
			for (i = 0; i < length; i++, key += keyStep);

		return buffer;
	}
	else
		return NULL;
}

void WriteData(HANDLE hFile, unsigned long length, unsigned char *data, int encrypted)
{
	unsigned char *buffer = NULL;
	unsigned long lwritten = 0;

	//don't bother making a copy if we're not going to encrypt it
	if (encrypted)
	{
		buffer = (unsigned char *) malloc(length);
		memcpy(buffer, data, length);
	}
	else
		buffer = data;

	int i;
	if (encrypted)
		for (i = 0; i < length; i++, key += keyStep)
			buffer[i] = buffer[i] ^ key;
	else //make sure the key is updated
		for (i = 0; i < length; i++, key += keyStep);

	WriteFile(hFile, buffer, length, &lwritten, NULL);

	if (encrypted)
		free(buffer);
}

unsigned char *readDir(struct tEntry **node, char *dirname, unsigned char *toc, unsigned long count)
{
	struct tEntry *current = NULL;
	int i = 0;

	for (i = 0; i < count; i++)
	{
		int x;
		char name[256];
		unsigned char type = 0;
		unsigned short *us = NULL;
		unsigned long *ul1 = NULL;
		unsigned long *ul2 = NULL;
		unsigned long *ul3 = NULL;
		unsigned long *ul4 = NULL;
		unsigned char *uc = NULL;


		type = *toc;
		toc++;
		us = (unsigned short *) toc;
		toc+=2;

		//get the name
		name[*us] = '\0';
		if (*us > 0)
		{
			memcpy(name, toc, *us);
			toc+=*us;
		}

		//ensure that the current node exists
		if (current == NULL)
		{
			*node = (struct tEntry*) malloc(sizeof(struct tEntry));
			current = *node;
		}
		else
		{
			current->next = (struct tEntry*) malloc(sizeof(struct tEntry));
			current = current->next;
		}

		current->children = NULL;
		current->next = NULL;

		if (type == 0)
		{
			char *newName = (char*) malloc(strlen(dirname) + strlen(name) + 1);
			strcpy(newName, dirname);
			strcat(newName, name);

			//file
			ul1 = (unsigned long *) toc;    //start
			ul2 = (unsigned long *) (toc+4);//length
			uc = (unsigned char *)  (toc+8); //encrypted?
			toc+=9;

			files++;

			//setup the node
			current->type = 0;
			current->filename = newName;
			current->offset = *ul1;
			current->length = *ul2;
			current->count = 0;
			current->encrypted = *uc;
		}
		else if (type == 1)
		{
			char *newName = (char*) malloc(strlen(dirname) + strlen(name) + 2);

			//dir
			ul1 = (unsigned long *) toc;    //start
			ul2 = (unsigned long *) (toc+4);//end
			ul3 = (unsigned long *) (toc+8);//???
			uc = (unsigned char *)  (toc+12);//???
			toc+=13;

			//create the full name
			strcpy(newName, dirname);
			strcat(newName, name);
			strcat(newName, "\\");

			//setup the node
			current->type = 1;
			current->filename = newName;
			current->offset = *ul1;
			current->length = *ul2;
			current->count = *ul3 >> 8;
			current->encrypted = *uc;

			//recursively parse the dir
			toc = readDir(&current->children, newName, toc, current->count);
		}
	}
	return toc;
}


void readTOC(unsigned char *toc)
{
	unsigned char type = 0;
	unsigned short *us = NULL;
	unsigned long *ul1 = NULL;
	unsigned long *ul2 = NULL;
	unsigned long *ul3 = NULL;
	unsigned char *uc = NULL;

	type = *toc;
	toc+=3;

	//printf ("found root node\n");
	//it should be the root node
	if (type == 1)
	{
		//dir
		ul1 = (unsigned long *) toc;//start
		ul2 = (unsigned long *) (toc+4);//end
		ul3 = (unsigned long *) (toc+8);
		uc = (unsigned char *) (toc+12);
		toc+=13;

		//printf("%u %u %u %d\n", *ul1, *ul2, *ul3, *uc);

		root.type = 2;
		root.children = NULL;
		root.next = NULL;

		readDir(&root.children, "", toc, *ul3 >> 8);
	}
}

unsigned long parseFile(HANDLE hFile)
{
	unsigned long fileSize = 0;
	unsigned long dataSize = 0;

	//get the overall filesize
	fileSize = GetFileSize(hFile, NULL);

	unsigned char *header = ReadData(hFile, 21, 1);

	//check the header
	if (header != NULL &&
		header[0] == 'n' &&
		header[1] == 'e' &&
		header[2] == 'x' &&
		header[3] == 'u' &&
		header[4] == 's')
	{
		dataSize = *((unsigned long *) (header + 12));

		free(header);

		//reset the file to the beginning
		resetFile(hFile);

		//calculate the toc size
		tocSize = fileSize - dataSize;

		//skip over the 'nexus' bit
		seekFile(hFile, 5);

		unsigned char *toc = ReadData(hFile, tocSize-5, 1);

		readTOC(toc);
	}
}

//extract
void extractFiles(HANDLE hFile, struct tEntry *node)
{
	unsigned long lwritten = 0;
	struct tEntry *walker = NULL;

	for (walker = node; walker != NULL; walker = walker->next)
	{
		if (walker->type == 0)
		{
			printf("Extracting %s...", walker->filename);
			seekFile(hFile, walker->offset + tocSize);
			unsigned char *file = ReadData(hFile, walker->length, walker->encrypted);
			HANDLE newFile = CreateFile(walker->filename, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
			WriteFile(newFile, file, walker->length, &lwritten, NULL);
			if (lwritten == walker->length)
				printf("ok\n");
			else
				printf("failed\n");
			CloseHandle(newFile);
			free(file);
		}
		else if (walker->type == 1)
		{
			printf("Creating directory %s...\n", walker->filename);
			CreateDirectory(walker->filename, NULL);
			if (walker->children)
				extractFiles(hFile, walker->children);
		}
	}
}

//print
void printFiles(struct tEntry *node)
{
	struct tEntry *walker = NULL;

	for (walker = node; walker != NULL; walker = walker->next)
	{
		if (walker->type == 0)
		{
			//printf("%d %8lu %8lu %8lu %s\n", walker->encrypted, walker->offset, walker->length, 0, walker->filename);
			printf("%s\n", walker->filename);
		}
		else if (walker->type == 1)
		{
			//printf("%d %8lu %8lu %8lu %s\n", walker->encrypted, walker->offset, walker->length, walker->count, walker->filename);
			//printf("%8lu %8lu %8lu %s\n", walker->count, walker->offset, walker->length, walker->filename);
			printf("%s\n", walker->filename);
			if (walker->children != NULL)
				printFiles(walker->children);
		}
	}
}

//create
unsigned long addDir(struct tEntry **node, char *dirname)
{
	BOOL            fFinished;
	HANDLE          hList;
	TCHAR           szDir[MAX_PATH+1];
	TCHAR           szSubDir[MAX_PATH+1];
	WIN32_FIND_DATA FileData;
	struct tEntry *current = NULL;
	unsigned long count = 0;

    // Get the proper directory path
	sprintf(szDir, "%s\\*", dirname);

    // Get the first file
    hList = FindFirstFile(szDir, &FileData);
    if (hList == INVALID_HANDLE_VALUE)
    {
        printf("No files found\n");
    }
    else
    {
        // Traverse through the directory structure
        fFinished = FALSE;
        while (!fFinished)
        {
			if ((strcmp(FileData.cFileName, ".") != 0) && (strcmp(FileData.cFileName, "..") != 0))
			{
				count++;
				//ensure that the current node exists
				if (current == NULL)
				{
					*node = (struct tEntry*) malloc(sizeof(struct tEntry));
					current = *node;
				}
				else
				{
					current->next = (struct tEntry*) malloc(sizeof(struct tEntry));
					current = current->next;
				}

				current->children = NULL;
				current->next = NULL;

	            // Check the object is a directory or not
	            if (FileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
	            {
	                // Get the full path for sub directory
	                sprintf(szSubDir, "%s\\%s", dirname, FileData.cFileName);

					//setup the node
					current->type = 1;
					current->filename = strdup(FileData.cFileName);
					current->fullFilename = strdup(szSubDir);
					current->offset = 0;
					current->length = 0;
					current->encrypted = 0;

					current->count = addDir(&current->children, szSubDir);
	            }
	            else
				{
	                sprintf(szSubDir, "%s\\%s", dirname, FileData.cFileName);
					//setup the node
					current->type = 0;
					current->filename = strdup(FileData.cFileName);
					current->fullFilename = strdup(szSubDir);
					current->offset = 0;
					current->length = FileData.nFileSizeLow;
					current->count = 0;
					current->encrypted = 0;
				}

			}
            if (!FindNextFile(hList, &FileData))
            {
                if (GetLastError() == ERROR_NO_MORE_FILES)
                {
                    fFinished = TRUE;
                }
            }
        }
    }

    FindClose(hList);

	return count;
}

unsigned long fixupTree(struct tEntry *node, unsigned long offset)
{
	struct tEntry *walker = NULL;

	for (walker = node; walker != NULL; walker = walker->next)
	{
		if (walker->type == 0)
		{
			//HACK to not encrypte the .tex or .msh files
			int length = strlen(walker->filename) - 4;
			char *ext = (walker->filename + length);
			if (strcmp(ext, ".tex") != 0 && strcmp(ext, ".msh") != 0 && strcmp(ext, ".TEX") != 0 && strcmp(ext, ".MSH") != 0)
				walker->encrypted = 1;
			else
				walker->encrypted = 0;
			//HACK end

			walker->offset = offset;

			offset += walker->length;
		}
		else if (walker->type == 1)
		{
			walker->encrypted = 0;
			walker->offset = offset;
			walker->count = walker->count << 8;

			if (walker->children != NULL)
			{
				offset = fixupTree(walker->children, offset);
				walker->length = offset - walker->offset;
			}
		}
	}

	return offset;
}

void writeTOC(HANDLE hFile, struct tEntry *node)
{
	struct tEntry *walker = NULL;

	for (walker = node; walker != NULL; walker = walker->next)
	{
		//toc entry size ==
		//1 + 2 + filename_length + 4 + 4 + 1 for a file
		//1 + 2 + filename_length + 4 + 4 + 4 + 1 for a dir or root
		int size = 0;
		int filenameLength = strlen(walker->filename);
		if (walker->type == 1 || walker->type == 2)
		{
			size = 16 + filenameLength;
		    walker->type == 1;
		}
		else
		{
			size = 12 + filenameLength;
		}

		unsigned char *tmp = (unsigned char *) malloc(size);

		tmp[0] = walker->type;
		*((unsigned short *) (tmp+1)) = filenameLength;
		if (filenameLength > 0)
			memcpy(tmp+3, walker->filename, filenameLength);
		*((unsigned long *) (tmp+filenameLength+3)) = walker->offset;
		*((unsigned long *) (tmp+filenameLength+7)) = walker->length;

		if (walker->type == 1)
		{
			*((unsigned long *) (tmp+filenameLength+11)) = walker->count;
			*((unsigned char *) (tmp+filenameLength+15)) = walker->encrypted;
		}
		else
		{
			*((unsigned char *) (tmp+filenameLength+11)) = walker->encrypted;
		}

	   	//printf("writing %8d %8d %8d %s\n", size, walker->offset, walker->length, walker->filename);
		WriteData(hFile, size, tmp, 1);

		free(tmp);

		if (walker->type == 1 && walker->children)
		{
			//printf(">>>\n");
			writeTOC(hFile, walker->children);
		}
	}
}

void writeFile(HANDLE hFile, struct tEntry *node)
{
	struct tEntry *walker = NULL;

	for (walker = node; walker != NULL; walker = walker->next)
	{
		if (walker->type == 0)
		{
			unsigned long size = 0;
			unsigned long lread = 0;
			unsigned char *buffer = NULL;

			printf("Writing %s...\n", walker->fullFilename);

			//open fullFilename
			HANDLE hSource = CreateFile(walker->fullFilename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);

			//read entire file into memory
			size = GetFileSize(hSource, NULL);
			buffer = (unsigned char *) malloc(size);
			ReadFile(hSource, buffer, size, &lread, NULL);

			//write file out to dat
			WriteData(hFile, size, buffer, walker->encrypted);

			//close file
			CloseHandle(hSource);

			//free buffer
			free(buffer);
		}
		else if (walker->type == 1 && walker->children)
		{
			//printf(">>>\n");
			writeFile(hFile, walker->children);
		}
	}
}

void createFile(HANDLE hFile)
{
	WriteData(hFile, 5, "nexus", 1);

	//write the toc
	writeTOC(hFile, &root);

	writeFile(hFile, &root);
}

void usage(char *file)
{
	printf("daTool v1.1 By elFarto\n");
	printf("Thanks to Sparky for figuring out the unknown number\n");
	printf("To extract a DAT file use:\n");
	printf("\t%s x <DAT file location>\n", file);

	printf("To list the contents of a DAT file use:\n");
	printf("\t%s l <DAT file location>\n", file);

	printf("To create a DAT file use:\n");
	printf("\t%s c <new DAT filename> file1 file2 file3 ... filen\n", file);
}

int main(int argc, char **argv)
{
	HANDLE hFile = NULL;
	char *exe = NULL;
	char *file = NULL;
	char mode = 'x';

	if (argc < 3)
	{
		usage(*argv);
		return -1;
	}

	exe = *argv;
	*argv++; argc--;
	mode = **argv;
	*argv++; argc--;
	file = *argv;
	*argv++; argc--;

	if (mode == 'x' || mode == 'l')
	{
		printf("Opening %s...", file);

		hFile = CreateFile(file, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);

		if (hFile == INVALID_HANDLE_VALUE)
		{
			printf("failed\n");
			return -1;
		}

		printf("ok\nParsing TOC...\n");
		parseFile(hFile);

		if (mode == 'x')
		{
			printf("Extracting...\n", files);
			extractFiles(hFile, root.children);
			printf("Extracted %u file(s)\n", files);
		}
		else if (mode == 'l')
		{
			printFiles(root.children);
		}

		CloseHandle(hFile);
	}
	else if (mode == 'c')
	{
		struct tEntry *current = NULL;

		if (argc == 0)
		{
			usage(exe);
			return -1;
		}

		//open the file here, so we don't create the whole structure, then say we can't open it
		hFile = CreateFile(file, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);

		if (hFile == INVALID_HANDLE_VALUE)
		{
			printf("Unable to open %s for writing\n", file);
			return -1;
		}
		//setup the root node
		root.type = 1;
		root.filename = "";
		root.count = argc << 8;
		root.encrypted = 0;
		root.next = NULL;

		for (;argc > 0; *argv++, argc--)
		{
			if (current == NULL)
			{
				current = (struct tEntry*) malloc(sizeof(struct tEntry));
				root.children = current;
			}
			else
			{
				current->next = (struct tEntry*) malloc(sizeof(struct tEntry));
				current = current->next;
			}

			current->type = 1;
			current->filename = strdup(*argv);
			current->children = NULL;
			current->next = NULL;

			current->count = addDir(&current->children, *argv);
		}

		root.length = fixupTree(root.children, 0);
		//printFiles(root.children);
		//printf("root length %lu\n", root.length);

		createFile(hFile);

		CloseHandle(hFile);
	}


	return 0;
}
